﻿using System;
using System.Collections.Generic;
using QuailDashboard.View.Models;

namespace QuailDashboard.View.Data
{
    /// <summary>
    /// Asynchronous hierarchal data finder
    /// </summary>
    /// <typeparam name="T">Type of heirarchal data</typeparam>
    public class HierarchalFinder<T> : Model where T : class, IHierarchalData
    {
        private readonly Stack<FindState> _workingState = new Stack<FindState>();
        private readonly IList<T> _rootList;
        private FindDirection _direction;
        private bool _isactive;
        private IHierarchalData _startingItem, _stoppingItem;

        /// <summary>
        /// Initializes a new instance of the <see cref="HierarchalFinder{T}"/> class.
        /// </summary>
        /// <param name="datalist">The list of items at the root level</param>
        public HierarchalFinder(IList<T> datalist)
        {
            _rootList = datalist;
            _direction = FindDirection.Ascending;
        }

        /// <summary>
        /// Delegate that should return true if the data should be added/processed/whatever
        /// </summary>
        /// <param name="data">the data to check</param>
        /// <returns>true or false</returns>
        public delegate bool FinderDelegate(T data);

        /// <summary>
        /// Event syntax for when the find data is complete
        /// </summary>
        /// <param name="sender">the object that started the find</param>
        /// <param name="foundtask">the task that was found, or null if no matching task was found</param>
        public delegate void OnFindDataCompleteEvent(object sender, T foundtask);

        /// <summary>
        /// Event that is fired when a Find is complete
        /// </summary>
        public event OnFindDataCompleteEvent FindDataComplete;

        /// <summary>
        /// How to find the task - ascending or descending
        /// </summary>
        public enum FindDirection
        {
            /// <summary>
            /// Find ascending (by date)
            /// </summary>
            Ascending,

            /// <summary>
            /// Find desceding (by date)
            /// </summary>
            Descending,
        }

        /// <summary>
        /// Gets a value indicating whether the find is active
        /// </summary>
        public bool IsActive
        {
            get
            {
                return _isactive;
            }

            private set
            {
                if (_isactive != value)
                {
                    _isactive = value;
                    NotifyPropertyChanged("IsActive");
                }
            }
        }

        /// <summary>
        /// Gets or sets the direction to search for a task
        /// </summary>
        public FindDirection Direction
        {
            get
            {
                return _direction;
            }

            set
            {
                if (_direction != value)
                {
                    _direction = value;
                    NotifyPropertyChanged("Direction");
                }
            }
        }

        /// <summary>
        /// Gets or sets the current search delegate
        /// </summary>
        public FinderDelegate Finder
        {
            get;
            set;
        }

        /// <summary>
        /// Searches for a task based on a delegate
        /// </summary>
        public void FindDataAsync()
        {
            FindDataAsync(default(T));
        }

        /// <summary>
        /// Searches for a task based on a delegate
        /// </summary>
        /// <param name="startingTask">The task to start the search</param>
        public void FindDataAsync(T startingTask)
        {
            if (IsActive)
                throw new Exception("Search already in progress.");

            if (Finder == null)
                throw new Exception("Finder delegate must be defined");

            _workingState.Clear();
            _workingState.Push(new FindState(_rootList));

            UpdateWorkingState(startingTask);
            FindInList(_workingState.Pop());
        }

        private void UpdateWorkingState(IHierarchalData startingTask)
        {
            _startingItem = startingTask;
            _stoppingItem = null;
            var state = _workingState.Peek();
            var keystack = new Stack<long>();

            while (startingTask != null)
            {
                keystack.Push(startingTask.Key);
                startingTask = startingTask.Parent;
            }

            while (keystack.Count > 0)
            {
                var key = keystack.Pop();
                var found = false;
                while (state.ActiveItem != null)
                {
                    if (state.ActiveItem.Key == key)
                    {
                        if (keystack.Count > 0)
                        {
                            if (!state.ActiveItem.IsPopulated)
                                throw new Exception("UpdateWorkingState not expecting to find an item with un-initialized child nodes");

                            state = new FindState(state.ActiveItem.Items as IList<T>);
                            _workingState.Push(state);
                        }

                        found = true;
                        break;
                    }

                    state.MoveNext();
                }

                if (!found)
                    throw new Exception("UpdateWorkingState - not able to find the task #" + key);
            }

            if (keystack.Count != 0)
            {
                throw new Exception("UpdateWorkingState - not able to find the desired starting task");
            }

            _workingState.Peek().CheckedItem = true;
        }

        private void FindInList(FindState state)
        {
            IsActive = true;
            _workingState.Push(state);

            if (Direction == FindDirection.Ascending)
                FindInListAscending(state);
            else
                FindInListDescending(state);
        }

        private void FindInListAscending(FindState state)
        {
            for (;;)
            {
                for (;;)
                {
                    var data = state.ActiveItem;

                    if (data == null)
                        break;

                    if (data == _stoppingItem)
                    {
                        // not found
                        InvokeFindComplete(default(T));
                        return;
                    }

                    if (!state.CheckedItem)
                    {
                        state.CheckedItem = true;
                        if (Finder(data))
                        {
                            InvokeFindComplete(data);
                            return;
                        }
                    }

                    if (data.IsPopulated)
                    {
                        if (data.Items.Count == 0)
                        {
                            if (!state.MoveNext())
                                break;

                            continue;
                        }

                        state = new FindState(data.Items as IList<T>);
                        state.MoveHead();
                        _workingState.Push(state);
                        continue;
                    }

                    data.PopulateComplete += OnTaskPopulateComplete;
                    data.Populate();

                    return;
                }

                // if I got here, there's nothing in ME or MY CHILDREN
                _workingState.Pop();

                if (_workingState.Count == 0)
                {
                    if (_startingItem == null)
                    {
                        // nothing left to check - bail
                        InvokeFindComplete(default(T));
                        return;
                    }

                    // start over at the top
                    state = new FindState(_rootList);
                    _workingState.Push(state);
                    _stoppingItem = _startingItem;
                    _startingItem = null;
                    continue;
                }

                // now, backwards-recursively, check my parent again
                state = _workingState.Peek();
                state.MoveNext();
            }
        }

        private void FindInListDescending(FindState state)
        {
            if (_startingItem == null)
            {
                InvokeFindComplete(default(T));
                return;
            }

            for (;;)
            {
                for (;;)
                {
                    var data = state.ActiveItem;

                    if (data == null || data.Key == _startingItem.Key)
                        break;

                    // check its children first
                    if (!data.IsPopulated)
                    {
                        data.PopulateComplete += OnTaskPopulateComplete;
                        data.Populate();
                        return;
                    }

                    if (data.Items.Count == 0)
                    {
                        if (!state.CheckedItem)
                        {
                            if (Finder(data))
                            {
                                state.CheckedItem = true;
                                InvokeFindComplete(data);
                                return;
                            }
                        }

                        if (!state.MovePrior())
                            break;

                        continue;
                    }

                    state = new FindState(data.Items as IList<T>);
                    state.MoveTail();
                    _workingState.Push(state);
                }

                // if I got here, there's nothing in ME or MY CHILDREN
                _workingState.Pop();

                if (_workingState.Count == 0)
                {
                    // nothing left to check - bail
                    InvokeFindComplete(default(T));
                    return;
                }

                // now, backwards-recursively, check my parent again
                state = _workingState.Peek();

                if (!state.CheckedItem)
                {
                    if (Finder(state.ActiveItem))
                    {
                        state.CheckedItem = true;
                        InvokeFindComplete(state.ActiveItem);
                        return;
                    }
                }

                state.MovePrior();
            }
        }

        private void OnTaskPopulateComplete(object sender, EventArgs e)
        {
            var state = _workingState.Pop();
            state.ActiveItem.PopulateComplete -= OnTaskPopulateComplete;
            FindInList(state);
        }

        private void InvokeFindComplete(T found)
        {
            IsActive = false;
            if (FindDataComplete != null)
                FindDataComplete(this, found);
        }

        /// <summary>
        /// Class to keep track of state during the find process
        /// </summary>
        private class FindState
        {
            private int _index;

            /// <summary>
            /// Initializes a new instance of the <see cref="FindState"/> class.
            /// </summary>
            /// <param name="list">The list of items</param>
            public FindState(IList<T> list)
            {
                List = list;
            }

            /// <summary>
            /// Gets or sets a value indicating whether the ActiveItem was checked
            /// </summary>
            public bool CheckedItem
            {
                get; set;
            }

            /// <summary>
            /// Gets the active item
            /// </summary>
            public T ActiveItem
            {
                get
                {
                    return IsItemValid ? List[_index] : default(T);
                }
            }

            /// <summary>
            /// Gets a value indicating whether the ActiveItem is valid
            /// </summary>
            private bool IsItemValid
            {
                get
                {
                    return _index >= 0 && _index < List.Count;
                }
            }

            /// <summary>
            /// Gets or sets the list of items at this level
            /// </summary>
            private IList<T> List
            {
                get;
                set;
            }

            /// <summary>
            /// Gets the next item in the list
            /// </summary>
            /// <returns>The next item or NULL if there are no additional items</returns>
            public bool MoveNext()
            {
                _index++;
                CheckedItem = false;

                return IsItemValid;
            }

            /// <summary>
            /// Gets the prior item in the list
            /// </summary>
            /// <returns>The prior item or NULL if there are no prior items</returns>
            public bool MovePrior()
            {
                _index--;
                CheckedItem = false;

                return IsItemValid;
            }

            /// <summary>
            /// Moves to the beginning of the list
            /// </summary>
            public void MoveHead()
            {
                _index = 0;
                CheckedItem = false;
            }

            /// <summary>
            /// Moves to the tail of the list
            /// </summary>
            public void MoveTail()
            {
                _index = List.Count - 1;
                CheckedItem = false;
            }
        }
    }
}
